home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / pine3.07 / c-client / mh.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-05-30  |  34.0 KB  |  1,280 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  *        Andrew Cohen
  13.  *        Internet: cohen@bucrf16.bu.edu
  14.  *
  15.  * Date:    23 February 1992
  16.  * Last Edited:    30 May 1992
  17.  *
  18.  * Copyright 1992 by the University of Washington
  19.  *
  20.  *  Permission to use, copy, modify, and distribute this software and its
  21.  * documentation for any purpose and without fee is hereby granted, provided
  22.  * that the above copyright notice appears in all copies and that both the
  23.  * above copyright notice and this permission notice appear in supporting
  24.  * documentation, and that the name of the University of Washington not be
  25.  * used in advertising or publicity pertaining to distribution of the software
  26.  * without specific, written prior permission.  This software is made
  27.  * available "as is", and
  28.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  29.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  30.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  31.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  32.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  33.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  34.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  35.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  36.  *
  37.  */
  38.  
  39. #include <stdio.h>
  40. #include <ctype.h>
  41. #include <pwd.h>
  42. #include <netdb.h>
  43. #include <errno.h>
  44. extern int errno;        /* just in case */
  45. #include <sys/types.h>
  46. #include <sys/dir.h>
  47. #include <sys/file.h>
  48. #include <sys/stat.h>
  49. #include <sys/time.h>
  50. #include <sys/uio.h>
  51. #include "osdep.h"
  52. #include "mail.h"
  53. #include "mh.h"
  54. #include "rfc822.h"
  55. #include "misc.h"
  56.  
  57. /* Netmh mail routines */
  58.  
  59.  
  60. /* Driver dispatch used by MAIL */
  61.  
  62. DRIVER mhdriver = {
  63.   (DRIVER *) NIL,        /* next driver */
  64.   mh_valid,            /* mailbox is valid for us */
  65.   mh_find,            /* find mailboxes */
  66.   mh_find_bboards,        /* find bboards */
  67.   mh_open,            /* open mailbox */
  68.   mh_close,            /* close mailbox */
  69.   mh_fetchfast,            /* fetch message "fast" attributes */
  70.   mh_fetchflags,        /* fetch message flags */
  71.   mh_fetchenvelope,        /* fetch message envelopes */
  72.   mh_fetchheader,        /* fetch message header only */
  73.   mh_fetchtext,            /* fetch message body only */
  74.   mh_fetchbody,            /* fetch message body section */
  75.   mh_setflag,            /* set message flag */
  76.   mh_clearflag,            /* clear message flag */
  77.   mh_search,            /* search for message based on criteria */
  78.   mh_ping,            /* ping mailbox to see if still alive */
  79.   mh_check,            /* check for new messages */
  80.   mh_expunge,            /* expunge deleted messages */
  81.   mh_copy,            /* copy messages to another mailbox */
  82.   mh_move,            /* move messages to another mailbox */
  83.   mh_gc                /* garbage collect stream */
  84. };
  85.  
  86. /* MH mail validate mailbox
  87.  * Accepts: mailbox name
  88.  * Returns: our driver if name is valid, otherwise calls valid in next driver
  89.  */
  90.  
  91. DRIVER *mh_valid (name)
  92.     char *name;
  93. {
  94.   return mh_isvalid (name) ? &mhdriver :
  95.     (mhdriver.next ? (*mhdriver.next->valid) (name) : NIL);
  96. }
  97.  
  98.  
  99. int mh_isvalid (name)
  100.     char *name;
  101. {
  102.   char tmp[MAILTMPLEN];
  103.   struct stat sbuf;
  104.                                 /* if file, get its status */
  105.   return (*name != '{' && (stat (mh_file (tmp,name),&sbuf) == 0) &&
  106.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  107. }
  108.  
  109. /* MH mail build file name
  110.  * Accepts: destination string
  111.  *          source
  112.  */
  113.  
  114. char *mh_file (dst,name)
  115.     char *dst;
  116.     char *name;
  117. {
  118.                 /* absolute path? */
  119.   if (*name == '/') strcpy (dst,name);
  120.                 /* relative path, goes in Mail folder */
  121.   else sprintf (dst,"%s/Mail/%s",getpwuid (geteuid ())->pw_dir,name);
  122.   return dst;
  123. }
  124.  
  125.  
  126. /* MH mail find list of mailboxes
  127.  * Accepts: mail stream
  128.  *        pattern to search
  129.  */
  130.  
  131. void mh_find (stream,pat)
  132.     MAILSTREAM *stream;
  133.     char *pat;
  134. {
  135.   int fd;
  136.   char tmp[MAILTMPLEN];
  137.   char *s,*t;
  138.   struct stat sbuf;
  139.                                 /* make file name */
  140.   sprintf (tmp,"%s/.mailboxlist",getpwuid (geteuid ())->pw_dir);
  141.   if ((fd = open (tmp,O_RDONLY,NIL)) >= 0) {
  142.     fstat (fd,&sbuf);           /* get file size and read data */
  143.     read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
  144.     close (fd);                 /* close file */
  145.     s[sbuf.st_size] = '\0';     /* tie off string */
  146.     if (t = strtok (s,"\n"))    /* get first mailbox name */
  147.       do if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  148.              mh_isvalid (t)) mm_mailbox (t);
  149.                                 /* for each mailbox */
  150.     while (t = strtok (NIL,"\n"));
  151.   }
  152. }
  153.  
  154.  
  155. /* MH mail find list of bboards
  156.  * Accepts: mail stream
  157.  *        pattern to search
  158.  */
  159.  
  160. void mh_find_bboards (stream,pat)
  161.     MAILSTREAM *stream;
  162.     char *pat;
  163. {
  164.   /* Always a no-op */
  165. }
  166.  
  167. /* MH mail open
  168.  * Accepts: stream to open
  169.  * Returns: stream on success, NIL on failure
  170.  */
  171.  
  172. MAILSTREAM *mh_open (stream)
  173.     MAILSTREAM *stream;
  174. {
  175.   int fd;
  176.   long i,nmsgs,j,k;
  177.   long recent = 0;
  178.   char tmp[MAILTMPLEN];
  179.   struct hostent *host_name;
  180.   struct direct **names;
  181.   struct stat sbuf;
  182.   if (LOCAL) {            /* close old file if stream being recycled */
  183.     mh_close (stream);        /* dump and save the changes */
  184.     stream->dtb = &mhdriver;    /* reattach this driver */
  185.   }
  186.   mh_file (tmp,stream->mailbox);/* canonicalize the stream mailbox name */
  187.   if (!strcmp (tmp,stream->mailbox)) {
  188.     fs_give ((void **) &stream->mailbox);
  189.     stream->mailbox = cpystr (tmp);
  190.   }
  191.                 /* scan directory */
  192.   if ((nmsgs = scandir (tmp,&names,mh_select,mh_numsort)) >= 0) {
  193.     stream->local = fs_get (sizeof (MHLOCAL));
  194.     LOCAL->dirty = NIL;        /* no update to .mhrc needed yet */
  195.     LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  196.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  197.     LOCAL->host = cpystr ((host_name = gethostbyname (tmp)) ?
  198.               host_name->h_name : tmp);
  199.                 /* create cache */
  200.     LOCAL->number = (unsigned long *) fs_get (nmsgs * sizeof (unsigned long));
  201.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  202.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  203.     LOCAL->date = (long *) fs_get (nmsgs * sizeof (long));
  204.     LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  205.     for (i = 0; i<nmsgs; ++i) {    /* initialize cache */
  206.       LOCAL->number[i] = atoi (names[i]->d_name);
  207.       fs_give ((void **) &names[i]);
  208.       LOCAL->header[i] = LOCAL->body[i] = NIL;
  209.       LOCAL->date[i] = 0;
  210.       LOCAL->seen[i] = NIL;
  211.     }
  212.     fs_give ((void **) &names);    /* free directory */
  213.                 /* make temporary buffer */
  214.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  215.     stream->sequence++;        /* bump sequence number */
  216.     stream->readonly = T;    /* make sure higher level knows readonly */
  217.     mail_exists (stream,nmsgs);    /* notify upper level that messages exist */
  218.  
  219.     while (i < nmsgs) {        /* mark all remaining messages as new */
  220.       LOCAL->seen[i++] = NIL;
  221.       mail_elt (stream,i)->recent = T;
  222.       ++recent;            /* count another recent message */
  223.     }
  224.     mail_recent (stream,recent);/* notify upper level about recent */
  225.                 /* notify if empty bboard */
  226.     if (!(stream->nmsgs || stream->silent)) mm_log ("folder is empty",WARN);
  227.   }
  228.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  229. }
  230.  
  231.  
  232. /* MH file name selection test
  233.  * Accepts: candidate directory entry
  234.  * Returns: T to use file name, NIL to skip it
  235.  */
  236.  
  237. int mh_select (name)
  238.     struct direct *name;
  239. {
  240.   char c;
  241.   char *s = name->d_name;
  242.   while (c = *s++) if (!isdigit (c)) return NIL;
  243.   return T;
  244. }
  245.  
  246.  
  247. /* MH file name comparision
  248.  * Accepts: first candidate directory entry
  249.  *        second candidate directory entry
  250.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  251.  */
  252.  
  253. int mh_numsort (d1,d2)
  254.     struct direct **d1;
  255.     struct direct **d2;
  256. {
  257.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  258. }
  259.  
  260. /* MH mail close
  261.  * Accepts: MAIL stream
  262.  */
  263.  
  264. void mh_close (stream)
  265.     MAILSTREAM *stream;
  266. {
  267.   long i;
  268.   if (LOCAL) {            /* only if a file is open */
  269.     mh_check (stream);        /* dump final checkpoint */
  270.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  271.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  272.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  273.     fs_give ((void **) &LOCAL->number);
  274.     fs_give ((void **) &LOCAL->header);
  275.     fs_give ((void **) &LOCAL->body);
  276.     fs_give ((void **) &LOCAL->date);
  277.     fs_give ((void **) &LOCAL->seen);
  278.                 /* free local scratch buffer */
  279.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  280.                 /* nuke the local data */
  281.     fs_give ((void **) &stream->local);
  282.     stream->dtb = NIL;        /* log out the DTB */
  283.   }
  284. }
  285.  
  286. /* MH mail fetch fast information
  287.  * Accepts: MAIL stream
  288.  *        sequence
  289.  */
  290.  
  291. void mh_fetchfast (stream,sequence)
  292.     MAILSTREAM *stream;
  293.     char *sequence;
  294. {
  295.   return;            /* no-op for local mail */
  296. }
  297.  
  298.  
  299. /* MH mail fetch flags
  300.  * Accepts: MAIL stream
  301.  *        sequence
  302.  */
  303.  
  304. void mh_fetchflags (stream,sequence)
  305.     MAILSTREAM *stream;
  306.     char *sequence;
  307. {
  308.   return;            /* no-op for local mail */
  309. }
  310.  
  311.  
  312. /* MH mail fetch envelope
  313.  * Accepts: MAIL stream
  314.  *        message # to fetch
  315.  * Returns: envelope of this message
  316.  *
  317.  * Fetches the "fast" information as well
  318.  */
  319.  
  320. ENVELOPE *mh_fetchenvelope (stream,msgno)
  321.     MAILSTREAM *stream;
  322.     long msgno;
  323. {
  324.   char *h,*t;
  325.   long i = msgno - 1;
  326.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  327.   if (!elt->env) {        /* make envelope now if don't have one */
  328.     h = mh_fetchheader (stream,msgno);
  329.                 /* can't use fetchtext since it'll set seen */
  330.     t = LOCAL->body[i] ? LOCAL->body[i] : "";
  331.     rfc822_parse_msg (&elt->env,&elt->body,h,strlen (h),t,strlen (t),
  332.               LOCAL->host,LOCAL->buf);
  333.   }
  334.   return elt->env;        /* return the envelope */
  335. }
  336.  
  337. /* MH mail fetch message header
  338.  * Accepts: MAIL stream
  339.  *        message # to fetch
  340.  * Returns: message header in RFC822 format
  341.  */
  342.  
  343. char *mh_fetchheader (stream,msgno)
  344.     MAILSTREAM *stream;
  345.     long msgno;
  346. {
  347.   unsigned long i,j;
  348.   int fd;
  349.   char *s,*b,*t;
  350.   long m = msgno - 1;
  351.   long lst = NIL;
  352.   struct stat sbuf;
  353.   struct tm *tm;
  354.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  355.                 /* build message file name */
  356.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[m]);
  357.   if (!LOCAL->header[m] && ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
  358.     fstat (fd,&sbuf);        /* get size of message */
  359.                 /* make plausible IMAPish date string */
  360.     tm = localtime (&sbuf.st_mtime);
  361.     sprintf (LOCAL->buf,"%2d-%s-%02d %2d:%02d:%02d-LCL",tm->tm_mday,
  362.          months[tm->tm_mon],tm->tm_year,tm->tm_hour,tm->tm_min,tm->tm_sec);
  363.     elt->internal_date = cpystr (LOCAL->buf);
  364.                 /* and local value for date comparisons */
  365.     LOCAL->date[m] = ((tm->tm_year-70)*12+tm->tm_mon)*31+tm->tm_mday-1;
  366.                 /* slurp message */
  367.     read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  368.     s[sbuf.st_size] = '\0';    /* tie off file */
  369.     close (fd);            /* flush message file */
  370.                 /* find end of header and count lines */
  371.     for (i=1,b=s; *b && !(lst && (*b == '\n'));) if (lst = (*b++ == '\n')) i++;
  372.                 /* copy header in CRLF form */
  373.     LOCAL->header[m] = (char *) fs_get (i += (j = b - s));
  374.     elt->rfc822_size = i - 1;    /* size of message header */
  375.     strcrlfcpy (&LOCAL->header[m],&i,s,j);
  376.                 /* copy body in CRLF form */
  377.     for (i = 1,t = b; *t;) if (*t++ == '\n') i++;
  378.     LOCAL->body[m] = (char *) fs_get (i += (j = t - b));
  379.     elt->rfc822_size += i - 1;    /* size of entire message */
  380.     strcrlfcpy (&LOCAL->body[m],&i,b,j);
  381.     fs_give ((void **) &s);    /* flush old data */
  382.   }
  383.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  384. }
  385.  
  386. /* MH mail fetch message text (only)
  387.     body only;
  388.  * Accepts: MAIL stream
  389.  *        message # to fetch
  390.  * Returns: message text in RFC822 format
  391.  */
  392.  
  393. char *mh_fetchtext (stream,msgno)
  394.     MAILSTREAM *stream;
  395.     long msgno;
  396. {
  397.   long i = msgno - 1;
  398.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  399.                 /* snarf message in case don't have it yet */
  400.   mh_fetchheader (stream,msgno);
  401.   if (!elt->seen) {        /* if message not seen before */
  402.     elt->seen = T;        /* mark as seen */
  403.     LOCAL->dirty = T;        /* and that stream is now dirty */
  404.   }
  405.   LOCAL->seen[i] = T;
  406.   return LOCAL->body[i] ? LOCAL->body[i] : "";
  407. }
  408.  
  409. /* MH fetch message body as a structure
  410.  * Accepts: Mail stream
  411.  *        message # to fetch
  412.  *        section specifier
  413.  *        pointer to length
  414.  * Returns: pointer to section of message body
  415.  */
  416.  
  417. char *mh_fetchbody (stream,m,s,len)
  418.     MAILSTREAM *stream;
  419.     long m;
  420.     char *s;
  421.     unsigned long *len;
  422. {
  423.   BODY *b;
  424.   PART *pt;
  425.   unsigned long i;
  426.   char *base = LOCAL->body[m - 1];
  427.   unsigned long offset = 0;
  428.   MESSAGECACHE *elt = mail_elt (stream,m);
  429.                 /* make sure have a body */
  430.   if (!(mh_fetchenvelope (stream,m) && (b = mail_elt (stream,m)->body) &&
  431.     s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  432.   do {                /* until find desired body part */
  433.                 /* multipart content? */
  434.     if (b->type == TYPEMULTIPART) {
  435.       pt = b->contents.part;    /* yes, find desired part */
  436.       while (--i && (pt = pt->next));
  437.       if (!pt) return NIL;    /* bad specifier */
  438.                 /* note new body, check valid nesting */
  439.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  440.       base += offset;        /* calculate new base */
  441.       offset = pt->offset;    /* and its offset */
  442.     }
  443.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  444.                 /* need to go down further? */
  445.     if (i = *s) switch (b->type) {
  446.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  447.       base += offset + b->contents.msg.offset;
  448.       offset = 0;        /* no offset any more */
  449.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  450.     case TYPEMULTIPART:        /* multipart, get next section */
  451.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  452.     default:            /* bogus subpart specification */
  453.       return NIL;
  454.     }
  455.   } while (i);
  456.                 /* lose if body bogus */
  457.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  458.   if (!elt->seen) {        /* if message not seen before */
  459.     elt->seen = T;        /* mark as seen */
  460.     LOCAL->dirty = T;        /* and that stream is now dirty */
  461.   }
  462.   LOCAL->seen[m-1] = T;
  463.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  464.               b->size.ibytes,b->encoding);
  465. }
  466.  
  467. /* MH mail set flag
  468.  * Accepts: MAIL stream
  469.  *        sequence
  470.  *        flag(s)
  471.  */
  472.  
  473. void mh_setflag (stream,sequence,flag)
  474.     MAILSTREAM *stream;
  475.     char *sequence;
  476.     char *flag;
  477. {
  478.   MESSAGECACHE *elt;
  479.   long i;
  480.   short f = mh_getflags (stream,flag);
  481.   short f1 = f & (fSEEN|fDELETED);
  482.   if (!f) return;        /* no-op if no flags to modify */
  483.                 /* get sequence and loop on it */
  484.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  485.     if ((elt = mail_elt (stream,i))->sequence) {
  486.                 /* set all requested flags */
  487.       if (f&fSEEN) elt->seen = T;
  488.       if (f&fDELETED) elt->deleted = T;
  489.       if (f&fFLAGGED) elt->flagged = T;
  490.       if (f&fANSWERED) elt->answered = T;
  491.       if (f1 && !LOCAL->seen[i - 1]) LOCAL->seen[i - 1] = LOCAL->dirty = T;
  492.     }
  493. }
  494.  
  495.  
  496. /* MH mail clear flag
  497.  * Accepts: MAIL stream
  498.  *        sequence
  499.  *        flag(s)
  500.  */
  501.  
  502. void mh_clearflag (stream,sequence,flag)
  503.     MAILSTREAM *stream;
  504.     char *sequence;
  505.     char *flag;
  506. {
  507.   MESSAGECACHE *elt;
  508.   long i = stream->nmsgs;
  509.   short f = mh_getflags (stream,flag);
  510.   short f1 = f & (fSEEN|fDELETED);
  511.   if (!f) return;        /* no-op if no flags to modify */
  512.                 /* get sequence and loop on it */
  513.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  514.     if ((elt = mail_elt (stream,i))->sequence) {
  515.                 /* clear all requested flags */
  516.       if (f&fSEEN) elt->seen = NIL;
  517.       if (f&fDELETED) elt->deleted = NIL;
  518.       if (f&fFLAGGED) elt->flagged = NIL;
  519.       if (f&fANSWERED) elt->answered = NIL;
  520.                 /* clearing either seen or deleted does this */
  521.       if (f1 && LOCAL->seen[i - 1]) {
  522.     LOCAL->seen[i - 1] = NIL;
  523.     LOCAL->dirty = T;    /* mark stream as dirty */
  524.       }
  525.     }
  526. }
  527.  
  528. /* MH mail search for messages
  529.  * Accepts: MAIL stream
  530.  *        search criteria
  531.  */
  532.  
  533. void mh_search (stream,criteria)
  534.     MAILSTREAM *stream;
  535.     char *criteria;
  536. {
  537.   long i,n;
  538.   char *d;
  539.   search_t f;
  540.                 /* initially all searched */
  541.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  542.                 /* get first criterion */
  543.   if (criteria && (criteria = strtok (criteria," "))) {
  544.                 /* for each criterion */
  545.     for (; criteria; (criteria = strtok (NIL," "))) {
  546.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  547.       switch (*ucase (criteria)) {
  548.       case 'A':            /* possible ALL, ANSWERED */
  549.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  550.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  551.     break;
  552.       case 'B':            /* possible BCC, BEFORE, BODY */
  553.     if (!strcmp (criteria+1,"CC"))
  554.       f = mh_search_string (mh_search_bcc,&d,&n);
  555.     else if (!strcmp (criteria+1,"EFORE"))
  556.       f = mh_search_date (mh_search_before,&n);
  557.     else if (!strcmp (criteria+1,"ODY"))
  558.       f = mh_search_string (mh_search_body,&d,&n);
  559.     break;
  560.       case 'C':            /* possible CC */
  561.     if (!strcmp (criteria+1,"C"))
  562.       f = mh_search_string (mh_search_cc,&d,&n);
  563.     break;
  564.       case 'D':            /* possible DELETED */
  565.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  566.     break;
  567.       case 'F':            /* possible FLAGGED, FROM */
  568.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  569.     else if (!strcmp (criteria+1,"ROM"))
  570.       f = mh_search_string (mh_search_from,&d,&n);
  571.     break;
  572.       case 'K':            /* possible KEYWORD */
  573.     if (!strcmp (criteria+1,"EYWORD"))
  574.       f = mh_search_flag (mh_search_keyword,&d);
  575.     break;
  576.       case 'N':            /* possible NEW */
  577.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  578.     break;
  579.  
  580.       case 'O':            /* possible OLD, ON */
  581.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  582.     else if (!strcmp (criteria+1,"N"))
  583.       f = mh_search_date (mh_search_on,&n);
  584.     break;
  585.       case 'R':            /* possible RECENT */
  586.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  587.     break;
  588.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  589.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  590.     else if (!strcmp (criteria+1,"INCE"))
  591.       f = mh_search_date (mh_search_since,&n);
  592.     else if (!strcmp (criteria+1,"UBJECT"))
  593.       f = mh_search_string (mh_search_subject,&d,&n);
  594.     break;
  595.       case 'T':            /* possible TEXT, TO */
  596.     if (!strcmp (criteria+1,"EXT"))
  597.       f = mh_search_string (mh_search_text,&d,&n);
  598.     else if (!strcmp (criteria+1,"O"))
  599.       f = mh_search_string (mh_search_to,&d,&n);
  600.     break;
  601.       case 'U':            /* possible UN* */
  602.     if (criteria[1] == 'N') {
  603.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  604.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  605.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  606.       else if (!strcmp (criteria+2,"KEYWORD"))
  607.         f = mh_search_flag (mh_search_unkeyword,&d);
  608.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  609.     }
  610.     break;
  611.       default:            /* we will barf below */
  612.     break;
  613.       }
  614.       if (!f) {            /* if can't determine any criteria */
  615.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  616.     mm_log (LOCAL->buf,ERROR);
  617.     return;
  618.       }
  619.                 /* run the search criterion */
  620.       for (i = 1; i <= stream->nmsgs; ++i)
  621.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  622.       mail_elt (stream,i)->searched = NIL;
  623.     }
  624.                 /* report search results to main program */
  625.     for (i = 1; i <= stream->nmsgs; ++i)
  626.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  627.   }
  628. }
  629.  
  630. /* MH mail ping mailbox
  631.  * Accepts: MAIL stream
  632.  * Returns: T if stream alive, else NIL
  633.  */
  634.  
  635. long mh_ping (stream)
  636.     MAILSTREAM *stream;
  637. {
  638.   return T;            /* always alive */
  639. }
  640.  
  641.  
  642. /* MH mail check mailbox
  643.  * Accepts: MAIL stream
  644.  */
  645.  
  646. void mh_check (stream)
  647.     MAILSTREAM *stream;
  648. {
  649.  /* A no-op for starters */
  650. }
  651.  
  652.  
  653. /* MH mail expunge mailbox
  654.  * Accepts: MAIL stream
  655.  */
  656.  
  657. void mh_expunge (stream)
  658.     MAILSTREAM *stream;
  659. {
  660.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  661. }
  662.  
  663. /* MH mail copy message(s)
  664.     s;
  665.  * Accepts: MAIL stream
  666.  *        sequence
  667.  *        destination mailbox
  668.  * Returns: T if copy successful, else NIL
  669.  */
  670.  
  671. long mh_copy (stream,sequence,mailbox)
  672.     MAILSTREAM *stream;
  673.     char *sequence;
  674.     char *mailbox;
  675. {
  676.   char tmp[MAILTMPLEN];
  677.   char lock[MAILTMPLEN];
  678.   struct iovec iov[3];
  679.   struct stat ssbuf,dsbuf;
  680.   char *t,*v;
  681.   int sfd,dfd;
  682.   long i;
  683.   long r = NIL;
  684.                 /* get sequence to do */
  685.   if (!mail_sequence (stream,sequence)) return NIL;
  686.                 /* get destination mailbox */
  687.   if ((dfd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  688.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  689.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  690.     mm_log (LOCAL->buf,ERROR);
  691.     return NIL;
  692.   }
  693.   mm_critical (stream);        /* go critical */
  694.   fstat (dfd,&dsbuf);        /* get current file size */
  695.   iov[2].iov_base = "\n\n";    /* constant trailer */
  696.   iov[2].iov_len = 2;
  697.  
  698.                 /* write all requested messages to mailbox */
  699.   for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
  700.                 /* build message file name */
  701.     sprintf (tmp,"%s/%lu",LOCAL->dir,LOCAL->number[i - 1]);
  702.     if ((sfd = open (tmp,O_RDONLY,NIL)) >= 0) {
  703.       fstat (sfd,&ssbuf);    /* get size of message */
  704.                 /* ensure enough room */
  705.       if (ssbuf.st_size > LOCAL->buflen) {
  706.                 /* fs_resize does an unnecessary copy */
  707.     fs_give ((void **) &LOCAL->buf);
  708.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ssbuf.st_size) + 1);
  709.       }
  710.                 /* slurp the silly thing in */
  711.       read (sfd,iov[1].iov_base = LOCAL->buf,iov[1].iov_len = ssbuf.st_size);
  712.                 /* tie off file */
  713.       iov[1].iov_base[ssbuf.st_size] = '\0';
  714.       close (sfd);        /* flush message file */
  715.                 /* get Path: data */
  716.       if ((((t = iov[1].iov_base - 1) && t[1] == 'P' && t[2] == 'a' &&
  717.         t[3] == 't' && t[4] == 'h' && t[5] == ':' && t[6] == ' ') ||
  718.        (t = strstr (iov[1].iov_base,"\nPath: "))) &&
  719.       (v = strchr (t += 7,'\n')) && (r = v - t)) {
  720.     strcpy (tmp,"From ");    /* start text */
  721.     strncpy (v = tmp+5,t,r);/* copy that many characters */
  722.     v[r++] = ' ';        /* delimiter */
  723.     v[r] = '\0';        /* tie it off */
  724.       }
  725.       else strcpy (tmp,"From somebody ");
  726.                 /* add the time and a newline */
  727.       strcat (tmp,ctime (&ssbuf.st_mtime));
  728.       iov[0].iov_len = strlen (iov[0].iov_base = tmp);
  729.                 /* now do the write */
  730.       if (r = (writev (dfd,iov,3) < 0)) {
  731.     sprintf (LOCAL->buf,"Message copy %d failed: %s",i,strerror (errno));
  732.     mm_log (LOCAL->buf,ERROR);
  733.     ftruncate (dfd,dsbuf.st_size);
  734.     break;            /* give up */
  735.       }
  736.     }
  737.   }
  738.   fsync (dfd);            /* force out the update */
  739.   bezerk_unlock (dfd,NIL,lock);    /* unlock and close mailbox */
  740.   mm_nocritical (stream);    /* release critical */
  741.   return !r;            /* return whether or not succeeded */
  742. }
  743.  
  744. /* MH mail move message(s)
  745.     s;
  746.  * Accepts: MAIL stream
  747.  *        sequence
  748.  *        destination mailbox
  749.  * Returns: T if move successful, else NIL
  750.  */
  751.  
  752. long mh_move (stream,sequence,mailbox)
  753.     MAILSTREAM *stream;
  754.     char *sequence;
  755.     char *mailbox;
  756. {
  757.   long i;
  758.   MESSAGECACHE *elt;
  759.   if (!(mail_sequence (stream,sequence) &&
  760.     mh_copy (stream,sequence,mailbox))) return NIL;
  761.                 /* delete all requested messages */
  762.   for (i = 1; i <= stream->nmsgs; i++)
  763.     if ((elt = mail_elt (stream,i))->sequence) {
  764.       elt->deleted = T;        /* mark message deleted */
  765.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  766.       LOCAL->seen[i - 1] = T;    /* and seen for .mhrc update */
  767.     }
  768.   return T;
  769. }
  770.  
  771.  
  772. /* MH garbage collect stream
  773.  * Accepts: Mail stream
  774.  *        garbage collection flags
  775.  */
  776.  
  777. void mh_gc (stream,gcflags)
  778.     MAILSTREAM *stream;
  779.     long gcflags;
  780. {
  781.   unsigned long i;
  782.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  783.                 /* flush texts from cache */
  784.     for (i = 0; i < stream->nmsgs; i++) {
  785.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  786.       if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  787.     }
  788. }
  789.  
  790. /* Internal routines */
  791.  
  792.  
  793. /* Parse flag list
  794.  * Accepts: MAIL stream
  795.  *        flag list as a character string
  796.  * Returns: flag command list
  797.  */
  798.  
  799. short mh_getflags (stream,flag)
  800.     MAILSTREAM *stream;
  801.     char *flag;
  802. {
  803.   char *t;
  804.   short f = 0;
  805.   short i,j;
  806.   if (flag && *flag) {        /* no-op if no flag string */
  807.                 /* check if a list and make sure valid */
  808.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  809.       mm_log ("Bad flag list",ERROR);
  810.       return NIL;
  811.     }
  812.                 /* copy the flag string w/o list construct */
  813.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  814.     LOCAL->buf[j] = '\0';
  815.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  816.  
  817.     while (*t) {        /* parse the flags */
  818.       if (*t == '\\') {        /* system flag? */
  819.     switch (*++t) {        /* dispatch based on first character */
  820.     case 'S':        /* possible \Seen flag */
  821.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  822.       t += 4;        /* skip past flag name */
  823.       break;
  824.     case 'D':        /* possible \Deleted flag */
  825.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  826.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  827.       t += 7;        /* skip past flag name */
  828.       break;
  829.     case 'F':        /* possible \Flagged flag */
  830.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  831.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  832.       t += 7;        /* skip past flag name */
  833.       break;
  834.     case 'A':        /* possible \Answered flag */
  835.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  836.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  837.       t += 8;        /* skip past flag name */
  838.       break;
  839.     default:        /* unknown */
  840.       i = 0;
  841.       break;
  842.     }
  843.                 /* add flag to flags list */
  844.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  845.     else {            /* bitch about bogus flag */
  846.       mm_log ("Unknown system flag",ERROR);
  847.       return NIL;
  848.     }
  849.       }
  850.       else {            /* no user flags yet */
  851.     mm_log ("Unknown flag",ERROR);
  852.     return NIL;
  853.       }
  854.     }
  855.   }
  856.   return f;
  857. }
  858.  
  859. /* Search support routines
  860.  * Accepts: MAIL stream
  861.  *        message number
  862.  *        pointer to additional data
  863.  *        pointer to temporary buffer
  864.  * Returns: T if search matches, else NIL
  865.  */
  866.  
  867. char mh_search_all (stream,msgno,d,n)
  868.     MAILSTREAM *stream;
  869.     long msgno;
  870.     char *d;
  871.     long n;
  872. {
  873.   return T;            /* ALL always succeeds */
  874. }
  875.  
  876.  
  877. char mh_search_answered (stream,msgno,d,n)
  878.     MAILSTREAM *stream;
  879.     long msgno;
  880.     char *d;
  881.     long n;
  882. {
  883.   return mail_elt (stream,msgno)->answered ? T : NIL;
  884. }
  885.  
  886.  
  887. char mh_search_deleted (stream,msgno,d,n)
  888.     MAILSTREAM *stream;
  889.     long msgno;
  890.     char *d;
  891.     long n;
  892. {
  893.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  894. }
  895.  
  896.  
  897. char mh_search_flagged (stream,msgno,d,n)
  898.     MAILSTREAM *stream;
  899.     long msgno;
  900.     char *d;
  901.     long n;
  902. {
  903.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  904. }
  905.  
  906.  
  907. char mh_search_keyword (stream,msgno,d,n)
  908.     MAILSTREAM *stream;
  909.     long msgno;
  910.     char *d;
  911.     long n;
  912. {
  913.   return NIL;            /* keywords not supported yet */
  914. }
  915.  
  916.  
  917. char mh_search_new (stream,msgno,d,n)
  918.     MAILSTREAM *stream;
  919.     long msgno;
  920.     char *d;
  921.     long n;
  922. {
  923.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  924.   return (elt->recent && !elt->seen) ? T : NIL;
  925. }
  926.  
  927. char mh_search_old (stream,msgno,d,n)
  928.     MAILSTREAM *stream;
  929.     long msgno;
  930.     char *d;
  931.     long n;
  932. {
  933.   return mail_elt (stream,msgno)->recent ? NIL : T;
  934. }
  935.  
  936.  
  937. char mh_search_recent (stream,msgno,d,n)
  938.     MAILSTREAM *stream;
  939.     long msgno;
  940.     char *d;
  941.     long n;
  942. {
  943.   return mail_elt (stream,msgno)->recent ? T : NIL;
  944. }
  945.  
  946.  
  947. char mh_search_seen (stream,msgno,d,n)
  948.     MAILSTREAM *stream;
  949.     long msgno;
  950.     char *d;
  951.     long n;
  952. {
  953.   return mail_elt (stream,msgno)->seen ? T : NIL;
  954. }
  955.  
  956.  
  957. char mh_search_unanswered (stream,msgno,d,n)
  958.     MAILSTREAM *stream;
  959.     long msgno;
  960.     char *d;
  961.     long n;
  962. {
  963.   return mail_elt (stream,msgno)->answered ? NIL : T;
  964. }
  965.  
  966.  
  967. char mh_search_undeleted (stream,msgno,d,n)
  968.     MAILSTREAM *stream;
  969.     long msgno;
  970.     char *d;
  971.     long n;
  972. {
  973.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  974. }
  975.  
  976.  
  977. char mh_search_unflagged (stream,msgno,d,n)
  978.     MAILSTREAM *stream;
  979.     long msgno;
  980.     char *d;
  981.     long n;
  982. {
  983.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  984. }
  985.  
  986.  
  987. char mh_search_unkeyword (stream,msgno,d,n)
  988.     MAILSTREAM *stream;
  989.     long msgno;
  990.     char *d;
  991.     long n;
  992. {
  993.   return T;            /* keywords not supported yet */
  994. }
  995.  
  996.  
  997. char mh_search_unseen (stream,msgno,d,n)
  998.     MAILSTREAM *stream;
  999.     long msgno;
  1000.     char *d;
  1001.     long n;
  1002. {
  1003.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1004. }
  1005.  
  1006. char mh_search_before (stream,msgno,d,n)
  1007.     MAILSTREAM *stream;
  1008.     long msgno;
  1009.     char *d;
  1010.     long n;
  1011. {
  1012.   return (char) (mh_msgdate (stream,msgno) < n);
  1013. }
  1014.  
  1015.  
  1016. char mh_search_on (stream,msgno,d,n)
  1017.     MAILSTREAM *stream;
  1018.     long msgno;
  1019.     char *d;
  1020.     long n;
  1021. {
  1022.   return (char) (mh_msgdate (stream,msgno) == n);
  1023. }
  1024.  
  1025.  
  1026. char mh_search_since (stream,msgno,d,n)
  1027.     MAILSTREAM *stream;
  1028.     long msgno;
  1029.     char *d;
  1030.     long n;
  1031. {
  1032.                 /* everybody interprets "since" as .GE. */
  1033.   return (char) (mh_msgdate (stream,msgno) >= n);
  1034. }
  1035.  
  1036.  
  1037. unsigned long mh_msgdate (stream,msgno)
  1038.     MAILSTREAM *stream;
  1039.     long msgno;
  1040. {
  1041.   struct stat sbuf;
  1042.   struct tm *tm;
  1043.   long i = msgno - 1;
  1044.   if (!LOCAL->date[i]) {    /* get cached date if don't have it */
  1045.                 /* build message file name */
  1046.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[i]);
  1047.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1048.     tm = localtime (&sbuf.st_mtime);
  1049.                 /* calculate value for date comparisons */
  1050.     LOCAL->date[i] = ((tm->tm_year-70)*12+tm->tm_mon)*31+tm->tm_mday-1;
  1051.   }
  1052.   return LOCAL->date[i];
  1053. }
  1054.  
  1055. char mh_search_body (stream,msgno,d,n)
  1056.     MAILSTREAM *stream;
  1057.     long msgno;
  1058.     char *d;
  1059.     long n;
  1060. {
  1061.   long i = msgno - 1;
  1062.   mh_fetchheader (stream,msgno);
  1063.   return LOCAL->body[i] ?
  1064.     search (LOCAL->body[i],strlen (LOCAL->body[i]),d,n) : NIL;
  1065. }
  1066.  
  1067.  
  1068. char mh_search_subject (stream,msgno,d,n)
  1069.     MAILSTREAM *stream;
  1070.     long msgno;
  1071.     char *d;
  1072.     long n;
  1073. {
  1074.   char *t = mh_fetchenvelope (stream,msgno)->subject;
  1075.   return t ? search (t,strlen (t),d,n) : NIL;
  1076. }
  1077.  
  1078.  
  1079. char mh_search_text (stream,msgno,d,n)
  1080.     MAILSTREAM *stream;
  1081.     long msgno;
  1082.     char *d;
  1083.     long n;
  1084. {
  1085.   char *t = mh_fetchheader (stream,msgno);
  1086.   return (t && search (t,strlen (t),d,n)) ||
  1087.     mh_search_body (stream,msgno,d,n);
  1088. }
  1089.  
  1090. char mh_search_bcc (stream,msgno,d,n)
  1091.     MAILSTREAM *stream;
  1092.     long msgno;
  1093.     char *d;
  1094.     long n;
  1095. {
  1096.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1097.                 /* get text for address */
  1098.   rfc822_write_address (LOCAL->buf,mh_fetchenvelope (stream,msgno)->bcc);
  1099.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1100. }
  1101.  
  1102.  
  1103. char mh_search_cc (stream,msgno,d,n)
  1104.     MAILSTREAM *stream;
  1105.     long msgno;
  1106.     char *d;
  1107.     long n;
  1108. {
  1109.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1110.                 /* get text for address */
  1111.   rfc822_write_address (LOCAL->buf,mh_fetchenvelope (stream,msgno)->cc);
  1112.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1113. }
  1114.  
  1115.  
  1116. char mh_search_from (stream,msgno,d,n)
  1117.     MAILSTREAM *stream;
  1118.     long msgno;
  1119.     char *d;
  1120.     long n;
  1121. {
  1122.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1123.                 /* get text for address */
  1124.   rfc822_write_address (LOCAL->buf,mh_fetchenvelope (stream,msgno)->from);
  1125.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1126. }
  1127.  
  1128.  
  1129. char mh_search_to (stream,msgno,d,n)
  1130.     MAILSTREAM *stream;
  1131.     long msgno;
  1132.     char *d;
  1133.     long n;
  1134. {
  1135.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1136.                 /* get text for address */
  1137.   rfc822_write_address (LOCAL->buf,mh_fetchenvelope (stream,msgno)->to);
  1138.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1139. }
  1140.  
  1141. /* Search parsers */
  1142.  
  1143.  
  1144. /* Parse a date
  1145.  * Accepts: function to return
  1146.  *        pointer to date integer to return
  1147.  * Returns: function to return
  1148.  */
  1149.  
  1150. search_t mh_search_date (f,n)
  1151.     search_t f;
  1152.     long *n;
  1153. {
  1154.   long i;
  1155.   char *t;
  1156.                 /* parse the date and return fn if OK */
  1157.   return (mh_search_string (f,&t,&i) && (*n = mh_date (t))) ? f : NIL;
  1158. }
  1159.  
  1160.  
  1161. /* Actual date parser (routine)
  1162.     minimal and stupid routine;
  1163.  * Accepts: date to parse
  1164.  * Returns: date integer
  1165.  */
  1166.  
  1167. long mh_date (s)
  1168.     char *s;
  1169. {
  1170.   long ms;
  1171.   long d,m,y;
  1172.                 /* parse first number (probable month) */
  1173.   if (!(s && (m = strtol ((const char *) s,&s,10)))) return NIL;
  1174.   switch (*s) {            /* different parse based on delimiter */
  1175.   case '/':            /* mm/dd/yy format */
  1176.                 /* parse remainder of date */
  1177.     if (!((d = strtol ((const char *) ++s,&s,10)) && *s == '/' &&
  1178.       (y = strtol ((const char *) ++s,&s,10)) && *s == '\0'))
  1179.       return NIL;
  1180.     break;
  1181.  
  1182.   case '-':            /* dd-mmm-yy format */
  1183.     d = m;            /* so the number we got is a day */
  1184.                 /* make sure string is UC and long enough! */
  1185.     if (strlen (ucase (s)) < 5) return NIL;
  1186.                 /* slurp up the month string */
  1187.     ms = (((long) s[1]) << 16) + (((long) s[2]) << 8) + s[3];
  1188.     switch (ms) {        /* determine the month */
  1189.     case ('J' << 16) + ('A' << 8) + 'N':
  1190.       m = 1; break;
  1191.     case ('F' << 16) + ('E' << 8) + 'B':
  1192.       m = 2; break;
  1193.     case ('M' << 16) + ('A' << 8) + 'R':
  1194.       m = 3; break;
  1195.     case ('A' << 16) + ('P' << 8) + 'R':
  1196.       m = 4; break;
  1197.     case ('M' << 16) + ('A' << 8) + 'Y':
  1198.       m = 5; break;
  1199.     case ('J' << 16) + ('U' << 8) + 'N':
  1200.       m = 6; break;
  1201.     case ('J' << 16) + ('U' << 8) + 'L':
  1202.       m = 7; break;
  1203.     case ('A' << 16) + ('U' << 8) + 'G':
  1204.       m = 8; break;
  1205.     case ('S' << 16) + ('E' << 8) + 'P':
  1206.       m = 9; break;
  1207.     case ('O' << 16) + ('C' << 8) + 'T':
  1208.       m = 10; break;
  1209.     case ('N' << 16) + ('O' << 8) + 'V':
  1210.       m = 11; break;
  1211.     case ('D' << 16) + ('E' << 8) + 'C':
  1212.       m = 12; break;
  1213.     default:
  1214.       return NIL;
  1215.     }
  1216.                 /* parse the year */
  1217.     if (s[4] == '-' && (y = (int) strtol ((const char *) s+5,&s,10)) &&
  1218.     (*s == '\0' || *s == ' ')) break;
  1219.   default:            /* unknown format */
  1220.     return NIL;
  1221.   }
  1222.   y -= (y >= 1900) ? 1970 : 70;    /* the world began for Unix in 1970 */
  1223.                 /* minimal validity check of date */
  1224.   if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0 || y >= 100) return NIL;
  1225.   return (y*12+m-1)*31+d-1;    /* calculate and return date value */
  1226. }
  1227.  
  1228. /* Parse a flag
  1229.  * Accepts: function to return
  1230.  *        pointer to string to return
  1231.  * Returns: function to return
  1232.  */
  1233.  
  1234. search_t mh_search_flag (f,d)
  1235.     search_t f;
  1236.     char **d;
  1237. {
  1238.                 /* get a keyword, return if OK */
  1239.   return (*d = strtok (NIL," ")) ? f : NIL;
  1240. }
  1241.  
  1242.  
  1243. /* Parse a string
  1244.  * Accepts: function to return
  1245.  *        pointer to string to return
  1246.  *        pointer to string length to return
  1247.  * Returns: function to return
  1248.  */
  1249.  
  1250. search_t mh_search_string (f,d,n)
  1251.     search_t f;
  1252.     char **d;
  1253.     long *n;
  1254. {
  1255.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1256.   if (c) {            /* better be an argument */
  1257.     switch (*c) {        /* see what the argument is */
  1258.     case '\0':            /* catch bogons */
  1259.     case ' ':
  1260.       return NIL;
  1261.     case '"':            /* quoted string */
  1262.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1263.     return NIL;
  1264.       break;
  1265.     case '{':            /* literal string */
  1266.       *n = strtol (c+1,&c,10);    /* get its length */
  1267.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1268.       *n > strlen (*d = c)) return NIL;
  1269.       c[*n] = '\255';        /* write new delimiter */
  1270.       strtok (c,"\255");    /* reset the strtok mechanism */
  1271.       break;
  1272.     default:            /* atomic string */
  1273.       *n = strlen (*d = strtok (c," "));
  1274.       break;
  1275.     }
  1276.     return f;
  1277.   }
  1278.   else return NIL;
  1279. }
  1280.